﻿using System;
using System.Linq;
using System.Collections.Generic;
using CodeCounsel.SharePoint.DevTools.Commands;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.VisualStudio.SharePoint.Commands;
using System.Text;
using System.IO;
using System.Globalization;
using System.Xml;
using System.Web.Compilation;
using System.Xml.Linq;

namespace CodeCounsel.SharePoint.DevTools.Commands
{
    public static class SharePointCommands
    {
        [SharePointCommand(CommandIDs.InstallFeature)]
        public static void InstallFeature(ISharePointCommandContext context, string featurePath)
        {
            SPFarm.Local.FeatureDefinitions.Add(featurePath, Guid.Empty, true);
        }
        
        [SharePointCommand(CommandIDs.UpgradeSolution)]
        public static void UpgradeSolution(ISharePointCommandContext context, SolutionInfo solutionInfo)
        {
            SPSolution solution = SPFarm.Local.Solutions[solutionInfo.Name];
            solution.Upgrade(solutionInfo.LocalPath, DateTime.Now);
        }

        [SharePointCommand(CommandIDs.GetWebApplicationFolders)]
        public static string[] GetWebApplicationFolders(ISharePointCommandContext context)
        {
            List<string> folders = new List<string>();

            SPWebApplication webApp = context.Site.WebApplication;
            foreach (KeyValuePair<SPUrlZone, SPIisSettings> iisSettingsByZone in webApp.IisSettings)
            {
                folders.Add(iisSettingsByZone.Value.Path.FullName);
            }
            return folders.ToArray();
        }

        [SharePointCommand(CommandIDs.CanCreateSite)]
        public static bool CanRecreateSite(ISharePointCommandContext context)
        {
            return true;
        }

        [SharePointCommand(CommandIDs.RecreateSite)]
        public static void RecreateSite(ISharePointCommandContext context, string defaultTemplate)
        {
            SPSite siteCollection = context.Site;
            SPWebApplication webApp = siteCollection.WebApplication;
            string template = siteCollection.RootWeb.WebTemplate;
            string url = siteCollection.ServerRelativeUrl;
            string ownerLogin = siteCollection.Owner.LoginName;
            string ownerEmail = siteCollection.Owner.Email;
            string ownerName = siteCollection.Owner.Name;
            uint lcid = siteCollection.RootWeb.Language;
            string title = siteCollection.RootWeb.Title;
            siteCollection.Delete();
            webApp.Sites.Add(url, title, null, lcid, defaultTemplate ?? template, ownerLogin, ownerName, ownerEmail);
        }

        [SharePointCommand(CommandIDs.IsFeatureEnabled)]
        public static bool IsFeatureEnabled(ISharePointCommandContext context, FeatureInfo featureID)
        {
            SPFeatureCollection featureCollection = GetFeatureCollectionForFeature(context, featureID);
            return featureCollection[featureID.FeatureID] != null;
        }

        [SharePointCommand(CommandIDs.GetFeatureDependencies)]
        public static FeatureDependencyInfo[] GetFeatureDependencies(ISharePointCommandContext context, FeatureInfo featureID)
        {
            List<FeatureDependencyInfo> dependencies = new List<FeatureDependencyInfo>();
            SPFeatureDefinition definition = SPFarm.Local.FeatureDefinitions[featureID.FeatureID];
            foreach (SPFeatureDependency dependency in definition.ActivationDependencies)
            {
                SPFeatureDefinition dependencyDefinition = SPFarm.Local.FeatureDefinitions[dependency.FeatureId];
                dependencies.Add(new FeatureDependencyInfo()
                {
                    FeatureID = dependency.FeatureId,
                    MinimumVersion = dependency.MinimumVersion.ToString(),
                    Title = dependencyDefinition.GetTitle(CultureInfo.CurrentCulture)
                });
            }
            return dependencies.ToArray();
        }

        class SPElementIDComparer : IEqualityComparer<SPElementDefinition>
        {
            public bool Equals(SPElementDefinition x, SPElementDefinition y)
            {
                return x.Id == y.Id;
            }

            public int GetHashCode(SPElementDefinition obj)
            {
                return obj.GetHashCode();
            }
        }

        [SharePointCommand(CommandIDs.GetElementDefinition)]
        public static string GetElementDefinition(ISharePointCommandContext context, 
            FeatureElementInfo elementInfo)
        {
            SPFeatureDefinition definition = SPFarm.Local.FeatureDefinitions[elementInfo.FeatureID];
            SPElementDefinition element = definition.GetElementDefinitions(CultureInfo.CurrentCulture)
                .OfType<SPElementDefinition>()
                .Where(searchElement => searchElement.ElementType == elementInfo.ElementType)
                .Where(searchElement => GetNameFromNode(searchElement.XmlDefinition) == elementInfo.Name)
                .FirstOrDefault();
            return element.XmlDefinition.OuterXml;
        }

        [SharePointCommand(CommandIDs.GetFeatureElements)]
        public static FeatureElementInfo[] GetFeatureElements(ISharePointCommandContext context, FeatureInfo feature)
        {
            List<FeatureElementInfo> elements = new List<FeatureElementInfo>();
            SPFeatureDefinition definition = SPFarm.Local.FeatureDefinitions[feature.FeatureID];
            foreach (SPElementDefinition element in 
                definition.GetElementDefinitions(CultureInfo.CurrentCulture)
                .OfType<SPElementDefinition>()
                .Distinct(new SPElementIDComparer()) )
            {
                elements.Add(new FeatureElementInfo()
                {
                    ElementID = element.Id,
                    UIVersion = element.UIVersion,
                    FeatureID = feature.FeatureID,
                    Name = GetNameFromNode(element.XmlDefinition),
                    ElementType = element.ElementType
                });
            }
            return elements.ToArray();
        }

        private static string GetNameFromNode(System.Xml.XmlNode xmlNode)
        {
            switch(xmlNode.Name){
                case "ContentType":
                    return GetRootAttribute(xmlNode, "Name") ;
                case "ContentTypeBinding":
                    return "ContentTypeBinding";
                case "Control":
                    return GetRootAttribute(xmlNode, "Id");
                case "CustomAction":
                    return GetRootAttribute(xmlNode, "Title");
                case "CustomActionGroup":
                    return GetRootAttribute(xmlNode, "Title");
                case "DocumentConverter":
                    return GetRootAttribute(xmlNode, "Name");
                case "FeatureSiteTemplateAssociation":
                    return "FeatureSiteTemplateAssociation";
                case "Field":
                    return GetRootAttribute(xmlNode, "Name");
                case "GroupMigrator":
                    return "GroupMigrator";
                case "HideCustomAction":
                    return GetRootAttribute(xmlNode, "Id");
                case "ListInstance":
                    return GetRootAttribute(xmlNode, "Title");
                case "ListTemplate":
                    return GetRootAttribute(xmlNode, "Name");
                case "Module":
                    return GetRootAttribute(xmlNode, "Name");
                case "PropertyBag":
                    return "PropertyBag";
                case "Receivers":
                    return "Receivers";
                case "UserMigrator":
                    return "UserMigrator";
                case "WebPartAdderExtension":
                    return "WebPartAdderExtension";
                case "WebTemplate":
                    return GetRootAttribute(xmlNode, "Name");
                case "Workflow":
                    return GetRootAttribute(xmlNode, "Name");
                case "WorkflowActions":
                    return "WorkflowActions";
                case "WorkflowAssociation":
                    return GetRootAttribute(xmlNode, "Name");
            }
            return null;
        }

        private static string GetRootAttribute(System.Xml.XmlNode xmlNode, string p)
        {
            XmlAttribute attribute = xmlNode.Attributes[p];
            return attribute != null ? attribute.Value : null;
        }

        [SharePointCommand(CommandIDs.EnableFeature)]
        public static void EnableFeature(ISharePointCommandContext context, FeatureInfo featureID)
        {
            SPFeatureCollection featureCollection = GetFeatureCollectionForFeature(context, featureID);
            featureCollection.Add(featureID.FeatureID, true);
        }

        [SharePointCommand(CommandIDs.DisableFeature)]
        public static void DisableFeature(ISharePointCommandContext context, FeatureInfo featureID)
        {
            SPFeatureCollection featureCollection = GetFeatureCollectionForFeature(context, featureID);
            featureCollection.Remove(featureID.FeatureID, true);
        }

        [SharePointCommand(CommandIDs.GetContentTypeID)]
        public static string GetContentTypeID(ISharePointCommandContext context, string name)
        {
            SPContentType type = context.Web.AvailableContentTypes[name];
            if (type == null)
            {
                throw new ArgumentException("Content Type does not exist");
            }
            return type.Id.ToString();
        }

        [SharePointCommand(CommandIDs.GetFields)]
        public static FieldInfo[] GetFields(ISharePointCommandContext context)
        {
            List<FieldInfo> fields = new List<FieldInfo>();
            foreach (SPField spField in context.Web.AvailableFields)
            {
                fields.Add(new FieldInfo()
                {
                    ID = spField.Id,
                    Name = spField.InternalName
                });
            }
            return fields.ToArray();
        }

        [SharePointCommand(CommandIDs.GetWebParts)]
        public static ItemInfo[] GetWebParts(ISharePointCommandContext context)
        {
            SPListTemplateType templateType = SPListTemplateType.WebPartCatalog;
            SPList webPartCatalog = context.Site.GetCatalog(templateType);
            return GetListItems(context.Web, webPartCatalog, null);
        }

        [SharePointCommand(CommandIDs.GetWebPartProperties)]
        public static Dictionary<string, string> GetWebPartProperties(ISharePointCommandContext context, ItemInfo webPart)
        {
            SPListTemplateType templateType = SPListTemplateType.WebPartCatalog;
            return GetListItemProperties(context.Web, webPart, templateType);
        }

        [SharePointCommand(CommandIDs.GetWebPartDefinition)]
        public static string GetWebPartDefinition(ISharePointCommandContext context, ItemInfo webPart)
        {
            SPListTemplateType templateType = SPListTemplateType.WebPartCatalog;
            SPList webPartCatalog = context.Site.GetCatalog(templateType);
            SPListItem webPartItem = webPartCatalog.GetItemByUniqueId(webPart.InternalID);
            using(StreamReader reader = new StreamReader(webPartItem.File.OpenBinaryStream(SPOpenBinaryOptions.None)))
            {
                return reader.ReadToEnd();
            }
        }

        [SharePointCommand(CommandIDs.GetThemes)]
        public static ItemInfo[] GetThemes(ISharePointCommandContext context)
        {
            SPListTemplateType templateType = SPListTemplateType.ThemeCatalog;
            SPList themeCatalog = context.Site.GetCatalog(templateType);
            SPQuery query = new SPQuery();
            query.Query = String.Format(
                @"<Where>
                    <BeginsWith>
                        <FieldRef Name='ContentTypeId' />
                        <Value Type='Text'>{0}</Value>
                    </BeginsWith>
                </Where>", "0x001FF87C4585753A468EEAB576974D1FE8");
            return GetListItems(context.Web, themeCatalog, query);
        }

        [SharePointCommand(CommandIDs.GetThemeProperties)]
        public static Dictionary<string, string> GetThemeProperties(ISharePointCommandContext context, ItemInfo theme)
        {
            SPListTemplateType templateType = SPListTemplateType.ThemeCatalog;
            return GetListItemProperties(context.Web, theme, templateType);
        }

        [SharePointCommand(CommandIDs.GetMasterPages)]
        public static ItemInfo[] GetMasterPages(ISharePointCommandContext context)
        {
            SPListTemplateType templateType = SPListTemplateType.MasterPageCatalog;
            SPList masterPageCatalog = context.Site.GetCatalog(templateType);
            SPQuery query = new SPQuery();
            query.Query = String.Format(
                @"<Where>
                    <BeginsWith>
                        <FieldRef Name='ContentTypeId' />
                        <Value Type='Text'>{0}</Value>
                    </BeginsWith>
                </Where>", SPBuiltInContentTypeId.MasterPage);
            return GetListItems(context.Web, masterPageCatalog, query);
        }

        [SharePointCommand(CommandIDs.GetMasterPageProperties)]
        public static Dictionary<string, string> GetMasterPageProperties(ISharePointCommandContext context, ItemInfo masterPage)
        {
            SPListTemplateType templateType = SPListTemplateType.MasterPageCatalog;
            return GetListItemProperties(context.Web, masterPage, templateType);
        }

        [SharePointCommand(CommandIDs.CopyAppBinContent)]
        public static void CopyAppBinContent(ISharePointCommandContext context)
        {
            SPServiceInstance localContent = SPWebServiceInstance.LocalContent;
            if ((localContent != null) && (localContent.Status == SPObjectStatus.Online))
            {
                SPWebService.ContentService.ApplyApplicationContentToLocalServer();
            }
            localContent = SPWebServiceInstance.LocalAdministration;
            if ((localContent != null) && (localContent.Status == SPObjectStatus.Online))
            {
                SPWebService.AdministrationService.ApplyApplicationContentToLocalServer();
            }
        }
        [SharePointCommand(CommandIDs.ParseUserControl)]
        public static string ParseUserControl(ISharePointCommandContext context, 
            CompilationInfo compilationInfo)
        {
            ClientBuildManagerParameter bmp = new ClientBuildManagerParameter();
            bmp.PrecompilationFlags = PrecompilationFlags.Clean | PrecompilationFlags.FixedNames | PrecompilationFlags.OverwriteTarget | PrecompilationFlags.ForceDebug; ;
            
            using (ClientBuildManager bm = new ClientBuildManager("/", compilationInfo.InFolder, compilationInfo.OutFolder, bmp))
            {
                bm.PrecompileApplication();
                string sourceFolder = bm.CodeGenDir;
                string compilationResultFile = Directory.GetFiles(sourceFolder, "*.compiled").First();
                XDocument compilationResult = XDocument.Load(compilationResultFile);
                string generatedTypeName = compilationResult.Root.Attribute("type").Value;
                string generatedClassName = generatedTypeName.Split('.').Last();
                foreach (string generatedSourceFile in Directory.GetFiles(sourceFolder, "*.cs"))
                {
                    string contents = File.ReadAllText(generatedSourceFile);
                    if (contents.Contains(String.Format("public class {0}", generatedClassName)))
                    {
                        return contents;
                    }
                }
            }
            throw new ApplicationException("Unknown");
        }

        [SharePointCommand(CommandIDs.CompileUserControl)]
        public static CompilationResult CompileUserControl(ISharePointCommandContext context, CompilationInfo compilationInfo)
        {
            ClientBuildManagerParameter bmp = new ClientBuildManagerParameter();
            bmp.PrecompilationFlags = PrecompilationFlags.AllowPartiallyTrustedCallers | PrecompilationFlags.Clean | PrecompilationFlags.FixedNames | PrecompilationFlags.OverwriteTarget;
            if (String.IsNullOrEmpty(compilationInfo.StrongNameKeyFile) == false)
            {
                bmp.StrongNameKeyFile = compilationInfo.StrongNameKeyFile;
            }
            using (ClientBuildManager bm = new ClientBuildManager("/", compilationInfo.InFolder, compilationInfo.OutFolder, bmp))
            {
                bm.PrecompileApplication();
            }
            string binFolder = Path.Combine(compilationInfo.OutFolder, "bin");
            string assemblyInfoFile = Directory.GetFiles(binFolder, "*.compiled").First();
            XDocument assemblyInfoDocument = XDocument.Load(assemblyInfoFile);
            string assemblyFileName = Path.Combine(
                binFolder,
                assemblyInfoDocument.Root.Attribute("assembly").Value + ".dll");
            string typeName = assemblyInfoDocument.Root.Attribute("type").Value;
            return new CompilationResult()
            {
                AssemblyPath = assemblyFileName,
                TypeName = typeName
            };
        }

        private static Dictionary<string, string> GetListItemProperties(SPWeb site, ItemInfo webPart, SPListTemplateType templateType)
        {
            Dictionary<string, string> properties = new Dictionary<string, string>();
            SPList webPartCatalog = site.Site.GetCatalog(templateType);
            SPListItem item = webPartCatalog.GetItemByUniqueId(webPart.InternalID);
            foreach (SPField field in item.Fields)
            {
                object value = item[field.Id];
                string valueString = value != null ? field.GetFieldValueAsText(value) : "";
                properties.Add(field.InternalName, valueString);
            }
            return properties;
        }

        [SharePointCommand(CommandIDs.GetFieldProperties)]
        public static Dictionary<string, string> GetFields(ISharePointCommandContext context, FieldInfo fieldInfo)
        {
            SPField field = context.Web.AvailableFields.GetFieldByInternalName(fieldInfo.Name);
            return SharePointCommandServices.GetProperties(field);
        }

        static SPFeatureCollection GetFeatureCollectionForFeature(ISharePointCommandContext context, FeatureInfo featureID)
        {
            if (featureID.Scope == FeatureScope.Web)
            {
                return context.Web.Features;
            }
            else if (featureID.Scope == FeatureScope.Site)
            {
                return context.Site.Features;
            }
            else if (featureID.Scope == FeatureScope.WebApplication)
            {
                return context.Site.WebApplication.Features;
            }
            else if (featureID.Scope == FeatureScope.Farm)
            {
                return SPWebService.ContentService.Features;
            }
            throw new ArgumentException("Unrecognized deployment scope.");
        }

        static ItemInfo[] GetListItems(SPWeb site, SPList list, SPQuery query)
        {
            SPListItemCollection items = query != null ? list.GetItems(query) : list.Items;
            List<ItemInfo> webParts = new List<ItemInfo>();
            foreach (SPListItem item in items)
            {
                webParts.Add(new ItemInfo()
                {
                    InternalID = item.UniqueId,
                    Name = item.Name,
                    Title = list.BaseType == SPBaseType.DocumentLibrary ? "" : item.Title
                });
            }
            return webParts.ToArray();
        }
    }
}